using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using ChessKit.ChessLogics;
using ChessKit.Common;
using log4net;

namespace EmraldClient
{
  [Export(typeof(IEmraldService))]
  public class EmraldService : IEmraldService
  {
    private static readonly ILog Log = LogManager.GetLogger(typeof(EmraldService));
    static EmraldService()
    {
      ServicePointManager.Expect100Continue = false;
    }

    public string UserName { get; private set; }

    private string _phpSessionId;
    private Uri _referer;
    private static readonly Random Rnd = new Random();

    public bool Logon(string userName, string password)
    {
      UserName = userName;

      if (_phpSessionId != null)
        throw new InvalidOperationException("Already logged on");

      GetPhpSessionId();

      var payload = string.Format("Username={0}&Password={1}&loginUser=login", userName, password);

      var request = CreateHttpRequest("/login.php");
      request.Method = "POST";
      request.ContentType = "application/x-www-form-urlencoded";
      request.ContentLength = payload.Length;
      request.AllowAutoRedirect = false;
      request.Headers.Add("Cache-Control", "max-age=0");
      request.Headers.Add("Origin", Url());
      request.Expect = null;

      using (var newStream = new StreamWriter(request.GetRequestStream()))
        newStream.Write(payload);

      var response = request.GetResponse();
      if (response.Headers["Location"] != Url("/Welcome.php"))
      {
        _phpSessionId = null;
        return false;
      }
      _referer = response.ResponseUri;
      return true;
    }
    public Problem GetProblem()
    {
      WebResponse response;
      do
      {
        var uri = string.Format("/chess.php?rand={0}", Rnd.Next());
        var request = CreateHttpRequest(uri);
        Log.Info("GetPropblem request is created: " + uri);
        response = request.GetResponse();
        Log.Info("GetPropblem request is sent");
        _referer = response.ResponseUri;
      }
      while (response.ResponseUri.AbsolutePath == "/Exit.php");

      if (response.ResponseUri.AbsolutePath == "/index.php")
      {
        Log.Warn("No luck! Session expired!");
        ResetState();
        throw new SessionExpiredException("logon again please");
      }
      Problem problem;
      try
      {
        problem = Parse(response);
      }
      catch (Exception x)
      {
        Log.Error(x);
        return null;
      }
      if (problem == null) Log.Warn("Boy, something went wrong! Here's the response: \r\n" + Print(response));
      else Log.Info("Good! Problem is here: \r\n" + problem);
      return problem;
    }

    private void ResetState()
    {
      _phpSessionId = null;
      _referer = null;
    }

    public void Logout()
    {
      var request = CreateHttpRequest("/logout.php");
      request.AllowAutoRedirect = false;
      request.GetResponse();
      ResetState();
    }
    public void SendResults(Problem problem)
    {
      var time = problem.IsSolved == true ? (int)(problem.Time.TotalSeconds * 1000) : -1;

      var uri = string.Format("/rating.php?user={0}" +
        "&position={1}&time={2}&last=true&PHPSESSID={3}&Mode=rated",
        UserName, problem.Id, time, _phpSessionId);

      var request = CreateHttpRequest(uri);
      request.AllowAutoRedirect = false;
      Log.Info("Send results: " + uri);
      var response = request.GetResponse();
      _referer = response.ResponseUri;
      Log.InfoFormat("Results are sent! \r\nResponseUri = {0}", _referer);
    }

    private static Problem Parse(WebResponse webResponse)
    {
      var boards = new List<Board>();
      var from = default(string);
      var problem = new Problem();
      var responseStream = webResponse.GetResponseStream();
      if (responseStream == null) return null;
      using (var r = new StreamReader(responseStream))
      {
        var line = r.ReadLine();
        while (line != null)
        {
          if (line.StartsWith("PosID"))
          {
            Log.Info(line);
            problem.Id = GetValue(line);
          }
          else if (line.StartsWith("ExStr"))
          {
            Log.Info(line);
            problem.Ex = TimeSpan.FromSeconds(
              double.Parse(GetValue(line), CultureInfo.InvariantCulture));
          }
          else if (line.StartsWith("SuccStr"))
          {
            Log.Info(line);
            problem.Succ = double.Parse(GetValue(line), CultureInfo.InvariantCulture);
          }
          else if (line.StartsWith("FailStr"))
          {
            Log.Info(line);
            problem.Fail = double.Parse(GetValue(line), CultureInfo.InvariantCulture);
          }
          else if (line.StartsWith("PosRating"))
          {
            Log.Info(line);
            problem.PosRating = int.Parse(GetValue(line), CultureInfo.InvariantCulture);
          }
          else if (line.StartsWith("PosRD"))
          {
            Log.Info(line);
            problem.PosRD = int.Parse(GetValue(line), CultureInfo.InvariantCulture);
          }
          else if (line.StartsWith("TacRating"))
          {
            Log.Info(line);
            problem.TacRating = int.Parse(GetValue(line), CultureInfo.InvariantCulture);
          }
          else if (line.StartsWith("TacRD"))
          {
            Log.Info(line);
            problem.TacRD = int.Parse(GetValue(line), CultureInfo.InvariantCulture);
          }
          else if (line.StartsWith("Boards["))
          {
            Log.Info(line);
            var position = GetValue(line);
            var fen = Utils.ToFen(position);
            Log.Info("Got FEN: " + fen);
            boards.Add(Board.FromFenString(fen));
            problem.AddPosition(fen);
          }
          else if (line.StartsWith("ActFrom["))
          {
            Log.Info(line);
            from = GetValue(line);
          }
          else if (line.StartsWith("ActTo["))
          {
            Log.Info(line);
            var move = new Move(Utils.ToPosition(from), Utils.ToPosition(GetValue(line)), PieceType.Queen);
            Log.Info("So the move is: " + move);
            var board = boards[problem.Moves.Count];
            var nextBoard = board.MakeMove(move);
            var v = nextBoard.PrevMove.ValidationProgress;
            if (v != ValidationProgress.Valid)
              Log.WarnFormat("Move is not valid! {0}", v);

            problem.AddMove(move);

            var actual = WithoutFancyStuff(nextBoard.ToFenString());
            var expexted = boards[problem.Moves.Count].ToFenString();
            if (actual != WithoutFancyStuff(expexted))
              Log.WarnFormat("Position is not expected: {0}", actual);
          }
          else if (line.StartsWith("FlipBoard"))
          {
            Log.Info(line);
            problem.IsReversed = GetValue(line) == "w";
            break;
          }
          line = r.ReadLine();
        }
      }
      return problem.Position == null ? null : problem;
    }
    private static string GetValue(string line)
    {
      var idx = line.IndexOf("\"");
      if (idx == -1) idx = line.IndexOf("'");
      if (idx == -1) return null;
      var res = line.Substring(idx + 1, line.Length - idx - 3);
      return res;
    }
    private static string WithoutFancyStuff(string fen)
    {
      var split = fen.Split(' ');
      return split[0] + " " + split[1];
    }

    private void GetPhpSessionId()
    {
      var request = CreateHttpRequest("/index.php");
      request.AllowAutoRedirect = false;
      var response = request.GetResponse();
      _referer = response.ResponseUri;
      var s = response.Headers["Set-Cookie"];
      var from = s.IndexOf('=');
      var to = s.IndexOf(';');
      _phpSessionId = s.Substring(from + 1, to - from - 1);
    }
    private HttpWebRequest CreateHttpRequest(string uri)
    {
      var request = (HttpWebRequest)WebRequest.Create(Url(uri));
      request.KeepAlive = true;
      request.Referer = _referer == null ? null : _referer.ToString();
      request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
      request.UserAgent = "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) " +
                          "AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7";
      request.Headers.Add("Accept-Encoding", "identity");
      request.Headers.Add("Accept-Language", "en-US,en;q=0.8");
      request.Headers.Add("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.3");
      request.Timeout = 8000;
      if (_phpSessionId != null)
      {
        request.Headers.Add("Cookie", string.Format("PHPSESSID={0}", _phpSessionId));
      }
      return request;
    }
    private static string Url(params string[] uri)
    {
      return "http://chess.emrald.net" + string.Join("", uri);
    }

    private static string Print(WebResponse response)
    {
      var sb = new StringBuilder();
      sb.AppendLine("Headers:");
      foreach (string key in response.Headers)
      {
        sb.AppendFormat("{0}: {1}", key, response.Headers[key]);
        sb.AppendLine();
      }
      sb.AppendLine("Content:");

      var responseStream = response.GetResponseStream();
      if (responseStream == null) return null;
      using (var r = new StreamReader(responseStream))
      {
        var line = r.ReadLine();
        while (line != null)
        {
          sb.AppendLine(line);
          line = r.ReadLine();
        }
      }
      return sb.ToString();
    }
  }
}